package bancosys.tec.persist.spring;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jmine.tec.persist.hibernate.filter.FiltersInjector;

import org.apache.log4j.Logger;
import org.hibernate.MappingException;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;
import org.hibernate.cfg.Configuration;
import org.hibernate.engine.FilterDefinition;
import org.hibernate.engine.SessionFactoryImplementor;
import org.hibernate.event.AutoFlushEventListener;
import org.hibernate.event.DirtyCheckEventListener;
import org.hibernate.event.FlushEntityEventListener;
import org.hibernate.event.FlushEventListener;
import org.hibernate.event.LoadEventListener;
import org.hibernate.event.MergeEventListener;
import org.hibernate.event.PostDeleteEventListener;
import org.hibernate.event.PostInsertEventListener;
import org.hibernate.event.PostLoadEventListener;
import org.hibernate.event.PostUpdateEventListener;
import org.hibernate.event.PreDeleteEventListener;
import org.hibernate.event.PreInsertEventListener;
import org.hibernate.event.PreLoadEventListener;
import org.hibernate.event.PreUpdateEventListener;
import org.hibernate.event.SaveOrUpdateEventListener;
import org.hibernate.type.BooleanType;
import org.hibernate.type.Type;
import org.springframework.context.ApplicationContextException;
import org.springframework.orm.hibernate3.LocalSessionFactoryBean;

import bancosys.tec.persist.Persistable;
import bancosys.tec.persist.hibernate.impl.annotations.AnnotationPartitionSessionFactory;

/**
 * Factory Bean do Spring para AnnotationSession do hibernate.
 * 
 * @created 12/02/2008
 * @author Gustavo Almeida
 */
public class AnnotationSessionFactoryBean extends LocalSessionFactoryBean {

    private List<Class<Persistable>> annotatedClasses = new ArrayList<Class<Persistable>>(0);

    private List<PostLoadEventListener> postLoadEventListeners;

    private List<PostInsertEventListener> postInsertEventListeners;

    private List<PostUpdateEventListener> postUpdateEventListeners;

    private List<PostDeleteEventListener> postDeleteEventListeners;

    private List<PreLoadEventListener> preLoadEventListeners;

    private List<PreInsertEventListener> preInsertEventListeners;

    private List<PreUpdateEventListener> preUpdateEventListeners;

    private List<PreDeleteEventListener> preDeleteEventListeners;

    private List<LoadEventListener> loadEventListeners;

    private List<MergeEventListener> mergeEventListeners;

    private List<SaveOrUpdateEventListener> saveEventListeners;

    private List<SaveOrUpdateEventListener> saveOrUpdateEventListeners;

    private List<FlushEventListener> flushEventListeners;

    private List<AutoFlushEventListener> autoFlushEventListeners;

    private List<FlushEntityEventListener> flushEntityEventListeners;

    private List<DirtyCheckEventListener> dirtyCheckEventListeners;

    private static final Logger LOG = Logger.getLogger(AnnotationSessionFactoryBean.class);

    private boolean injectFiltersOnSubclasses = true;

    /**
     * Devolve as classes anotadas.
     * 
     * @return classes anotadas.
     */
    public List<Class<Persistable>> getAnnotatedClasses() {
        return this.annotatedClasses;
    }

    /**
     * Define as classes anotadas.
     * 
     * @param classes classes anotadas
     */
    public void setAnnotatedClasses(List<Class<Persistable>> classes) {
        this.annotatedClasses = classes;
    }

    /**
     * Define o postLoadEventListeners.
     * 
     * @param postLoadEventListeners O postLoadEventListeners a ser definido.
     */
    public void setPostLoadEventListeners(List<PostLoadEventListener> postLoadEventListeners) {
        this.postLoadEventListeners = postLoadEventListeners;
    }

    /**
     * Define o postInsertEventListeners.
     * 
     * @param postInsertEventListeners O postInsertEventListeners a ser definido.
     */
    public void setPostInsertEventListeners(List<PostInsertEventListener> postInsertEventListeners) {
        this.postInsertEventListeners = postInsertEventListeners;
    }

    /**
     * Define o postUpdateEventListeners.
     * 
     * @param postUpdateEventListeners O postUpdateEventListeners a ser definido.
     */
    public void setPostUpdateEventListeners(List<PostUpdateEventListener> postUpdateEventListeners) {
        this.postUpdateEventListeners = postUpdateEventListeners;
    }

    /**
     * Define o postDeleteEventListeners.
     * 
     * @param postDeleteEventListeners O postUpdateEventListeners a ser definido.
     */
    public void setPostDeleteEventListeners(List<PostDeleteEventListener> postDeleteEventListeners) {
        this.postDeleteEventListeners = postDeleteEventListeners;
    }

    /**
     * Define o preLoadEventListeners.
     * 
     * @param preLoadEventListeners O preLoadEventListeners a ser definido.
     */
    public void setPreLoadEventListeners(List<PreLoadEventListener> preLoadEventListeners) {
        this.preLoadEventListeners = preLoadEventListeners;
    }

    /**
     * Define o preInsertEventListeners.
     * 
     * @param preInsertEventListeners O preInsertEventListeners a ser definido.
     */
    public void setPreInsertEventListeners(List<PreInsertEventListener> preInsertEventListeners) {
        this.preInsertEventListeners = preInsertEventListeners;
    }

    /**
     * Define o preUpdateEventListeners.
     * 
     * @param preUpdateEventListeners O preUpdateEventListeners a ser definido.
     */
    public void setPreUpdateEventListeners(List<PreUpdateEventListener> preUpdateEventListeners) {
        this.preUpdateEventListeners = preUpdateEventListeners;
    }

    /**
     * Define o preDeleteEventListeners.
     * 
     * @param preDeleteEventListeners O preUpdateEventListeners a ser definido.
     */
    public void setPreDeleteEventListeners(List<PreDeleteEventListener> preDeleteEventListeners) {
        this.preDeleteEventListeners = preDeleteEventListeners;
    }

    /**
     * Define o loadEventListeners.
     * 
     * @param loadEventListeners O loadEventListeners a ser definido.
     */
    public void setLoadEventListeners(List<LoadEventListener> loadEventListeners) {
        this.loadEventListeners = loadEventListeners;
    }

    /**
     * Define o mergeEventListeners.
     * 
     * @param mergeEventListeners o mergeEventListeners a ser definido.
     */
    public void setMergeEventListeners(List<MergeEventListener> mergeEventListeners) {
        this.mergeEventListeners = mergeEventListeners;
    }

    /**
     * Define o saveEventListener.
     * 
     * @param saveEventListeners o saveEventListener a ser definido.
     */
    public void setSaveEventListeners(List<SaveOrUpdateEventListener> saveEventListeners) {
        this.saveEventListeners = saveEventListeners;
    }

    /**
     * Define o saveOrUpdateEventListener.
     * 
     * @param saveOrUpdateEventListeners o saveOrUpdateEventListener a ser definido.
     */
    public void setSaveOrUpdateEventListeners(List<SaveOrUpdateEventListener> saveOrUpdateEventListeners) {
        this.saveOrUpdateEventListeners = saveOrUpdateEventListeners;
    }

    /**
     * Define o flushEventListeners.
     * 
     * @param flushEventListeners o flushEventListeners a ser definido.
     */
    public void setFlushEventListeners(List<FlushEventListener> flushEventListeners) {
        this.flushEventListeners = flushEventListeners;
    }

    /**
     * Define o autoFlushEventListeners.
     * 
     * @param autoFlushEventListeners o autoFlushEventListeners a ser definido.
     */
    public void setAutoFlushEventListeners(List<AutoFlushEventListener> autoFlushEventListeners) {
        this.autoFlushEventListeners = autoFlushEventListeners;
    }

    /**
     * Define o flushEntityEventListeners.
     * 
     * @param flushEntityEventListeners o flushEntityEventListeners a ser definido.
     */
    public void setFlushEntityEventListeners(List<FlushEntityEventListener> flushEntityEventListeners) {
        this.flushEntityEventListeners = flushEntityEventListeners;
    }

    /**
     * Define o dirtyCheckEventListeners.
     * 
     * @param dirtyCheckEventListeners o dirtyCheckEventListeners a ser definido.
     */
    public void setDirtyCheckEventListeners(List<DirtyCheckEventListener> dirtyCheckEventListeners) {
        this.dirtyCheckEventListeners = dirtyCheckEventListeners;
    }

    /**
     * Inicializa a configuração com os listeners.
     * 
     * @return a configuração do hibernate
     * @see LocalSessionFactoryBean
     */
    @Override
    protected Configuration newConfiguration() {
        Configuration config = super.newConfiguration();
        if (!(config instanceof AnnotationConfiguration)) {
            throw new ApplicationContextException("persist.notAnnotationConfiguration");
        }
        AnnotationConfiguration annotationConfig = (AnnotationConfiguration) config;

        this.addListeners(annotationConfig);
        this.addAuthorizationFilter(annotationConfig);
        this.addAnnotatedClasses(annotationConfig);

        return annotationConfig;
    }

    /**
     * Registra as classes persistíveis na configuração da SessionFactory.
     * 
     * @param annotationConfig configuração da SessionFactory.
     */
    private void addAnnotatedClasses(AnnotationConfiguration annotationConfig) {
        int errors = 0;
        for (Class<Persistable> clazz : this.annotatedClasses) {
            try {
                annotationConfig.addAnnotatedClass(clazz);
            } catch (MappingException e) {
                errors++;
                LOG.error("Unable to add class '" + clazz.getName() + "' to hibernate configuration.", e);
            }
        }
        if (errors > 0) {
            StringBuilder sb = new StringBuilder("Hibernate configuration error ( ");
            sb.append(errors);
            sb.append(errors > 1 ? " errors" : " error");
            sb.append(" ), see log for details");
            throw new ApplicationContextException(sb.toString());
        }
    }

    /**
     * Registra o filtro de autorização. (Feito/Conferido) na configuração da SessionFactory.
     * 
     * @param annotationConfig configuração da SessionFactory.
     */
    private void addAuthorizationFilter(AnnotationConfiguration annotationConfig) {
        Map<String, Type> paramTypes = new HashMap<String, Type>();
        paramTypes.put(AnnotationPartitionSessionFactory.PARAMETER_NAME, new BooleanType());
        FilterDefinition filterDefinition =
                new FilterDefinition(AnnotationPartitionSessionFactory.FILTER_NAME, AnnotationPartitionSessionFactory.PARAMETER_NAME
                        + " = :" + AnnotationPartitionSessionFactory.PARAMETER_NAME, paramTypes);
        annotationConfig.addFilterDefinition(filterDefinition);
    }

    /**
     * Registra os listeners na configuração da SessionFactory.
     * 
     * @param annotationConfig configuração da SessionFactory.
     */
    private void addListeners(AnnotationConfiguration annotationConfig) {
        // post load
        if (this.postLoadEventListeners != null && this.postLoadEventListeners.size() != 0) {
            PostLoadEventListener[] listeners =
                    this.postLoadEventListeners.toArray(new PostLoadEventListener[this.postLoadEventListeners.size()]);
            annotationConfig.getEventListeners().setPostLoadEventListeners(listeners);
        }

        // post insert
        if (this.postInsertEventListeners != null && this.postInsertEventListeners.size() != 0) {
            PostInsertEventListener[] listeners =
                    this.postInsertEventListeners.toArray(new PostInsertEventListener[this.postInsertEventListeners.size()]);
            annotationConfig.getEventListeners().setPostInsertEventListeners(listeners);
        }

        // post update
        if (this.postUpdateEventListeners != null && this.postUpdateEventListeners.size() != 0) {
            PostUpdateEventListener[] listeners =
                    this.postUpdateEventListeners.toArray(new PostUpdateEventListener[this.postUpdateEventListeners.size()]);
            annotationConfig.getEventListeners().setPostUpdateEventListeners(listeners);
        }

        // post delete
        if (this.postDeleteEventListeners != null && this.postDeleteEventListeners.size() != 0) {
            PostDeleteEventListener[] listeners =
                    this.postDeleteEventListeners.toArray(new PostDeleteEventListener[this.postDeleteEventListeners.size()]);
            annotationConfig.getEventListeners().setPostDeleteEventListeners(listeners);
        }

        // pre load
        if (this.preLoadEventListeners != null && this.preLoadEventListeners.size() != 0) {
            PreLoadEventListener[] listeners =
                    this.preLoadEventListeners.toArray(new PreLoadEventListener[this.preLoadEventListeners.size()]);
            annotationConfig.getEventListeners().setPreLoadEventListeners(listeners);
        }

        // pre insert
        if (this.preInsertEventListeners != null && this.preInsertEventListeners.size() != 0) {
            PreInsertEventListener[] listeners =
                    this.preInsertEventListeners.toArray(new PreInsertEventListener[this.preInsertEventListeners.size()]);
            annotationConfig.getEventListeners().setPreInsertEventListeners(listeners);
        }

        // pre updates
        if (this.preUpdateEventListeners != null && this.preUpdateEventListeners.size() != 0) {
            PreUpdateEventListener[] listeners =
                    this.preUpdateEventListeners.toArray(new PreUpdateEventListener[this.preUpdateEventListeners.size()]);
            annotationConfig.getEventListeners().setPreUpdateEventListeners(listeners);
        }

        // pre delete
        if (this.preDeleteEventListeners != null && this.preDeleteEventListeners.size() != 0) {
            PreDeleteEventListener[] listeners =
                    this.preDeleteEventListeners.toArray(new PreDeleteEventListener[this.preDeleteEventListeners.size()]);
            annotationConfig.getEventListeners().setPreDeleteEventListeners(listeners);
        }

        // load
        if (this.loadEventListeners != null && this.loadEventListeners.size() != 0) {
            LoadEventListener[] listeners = this.loadEventListeners.toArray(new LoadEventListener[this.loadEventListeners.size()]);
            annotationConfig.getEventListeners().setLoadEventListeners(listeners);
        }

        // merge
        if (this.mergeEventListeners != null && this.mergeEventListeners.size() != 0) {
            MergeEventListener[] listeners = this.mergeEventListeners.toArray(new MergeEventListener[this.mergeEventListeners.size()]);
            annotationConfig.getEventListeners().setMergeEventListeners(listeners);
        }

        // save or update
        if (this.saveEventListeners != null && this.saveEventListeners.size() != 0) {
            SaveOrUpdateEventListener[] listeners =
                    this.saveEventListeners.toArray(new SaveOrUpdateEventListener[this.saveEventListeners.size()]);
            annotationConfig.getEventListeners().setSaveEventListeners(listeners);
        }

        // save or update
        if (this.saveOrUpdateEventListeners != null && this.saveOrUpdateEventListeners.size() != 0) {
            SaveOrUpdateEventListener[] listeners =
                    this.saveOrUpdateEventListeners.toArray(new SaveOrUpdateEventListener[this.saveOrUpdateEventListeners.size()]);
            annotationConfig.getEventListeners().setSaveOrUpdateEventListeners(listeners);
        }

        // flush
        if (this.flushEventListeners != null && this.flushEventListeners.size() != 0) {
            FlushEventListener[] listeners = this.flushEventListeners.toArray(new FlushEventListener[this.flushEventListeners.size()]);
            annotationConfig.getEventListeners().setFlushEventListeners(listeners);
        }

        // auto flush
        if (this.autoFlushEventListeners != null && this.autoFlushEventListeners.size() != 0) {
            AutoFlushEventListener[] listeners =
                    this.autoFlushEventListeners.toArray(new AutoFlushEventListener[this.autoFlushEventListeners.size()]);
            annotationConfig.getEventListeners().setAutoFlushEventListeners(listeners);
        }

        // flush entity
        if (this.flushEntityEventListeners != null && this.flushEntityEventListeners.size() != 0) {
            FlushEntityEventListener[] listeners =
                    this.flushEntityEventListeners.toArray(new FlushEntityEventListener[this.flushEntityEventListeners.size()]);
            annotationConfig.getEventListeners().setFlushEntityEventListeners(listeners);
        }

        // dirty check
        if (this.dirtyCheckEventListeners != null && this.dirtyCheckEventListeners.size() != 0) {
            DirtyCheckEventListener[] listeners =
                    this.dirtyCheckEventListeners.toArray(new DirtyCheckEventListener[this.dirtyCheckEventListeners.size()]);
            annotationConfig.getEventListeners().setDirtyCheckEventListeners(listeners);
        }
    }

    /**
     * Devolve a uma SessionFactory.
     * 
     * @return uma SessionFactory. see FactoryBean
     */
    @Override
    public Object getObject() {
        SessionFactory sessionFactory = (SessionFactory) super.getObject();
        if(this.isInjectFiltersOnSubclasses()) {
            try {
                FiltersInjector.injectFiltersOnSubclasses((SessionFactoryImplementor) sessionFactory);
                FiltersInjector.qualifyTableNamesOnFilters((SessionFactoryImplementor) sessionFactory, getConfiguration());
            } catch (NoSuchFieldException e) {
                throw new IllegalStateException(e);
            } catch (IllegalAccessException e) {
                throw new IllegalStateException(e);
            }
        }
        return sessionFactory;
    }

    /**
     * @return the injectFiltersOnSubclasses. Defaults to true
     */
    public boolean isInjectFiltersOnSubclasses() {
        return injectFiltersOnSubclasses;
    }

    /**
     * @param injectFiltersOnSubclasses the injectFiltersOnSubclasses to set
     */
    public void setInjectFiltersOnSubclasses(boolean injectFiltersOnSubclasses) {
        this.injectFiltersOnSubclasses = injectFiltersOnSubclasses;
    }
}
